5
5
.
.
1
1
2
2
.
.
3
3
D
D
r
r
a
a
g
g
I
I
n
n
f
f
o
o
This tutorial shows how to use .gesture( DragGesture() ).
Content
.updating() - Move View while dragging (Called during dragging. View goes to initial position after dragging)
.onEnded() - Move View when dragging ends (Called when we finish dragging)
Calculate distance from target (drag green circle near the red circle)
Combine dragging and staying (when you finish dragging)
Combine dragging, staying and distance (combines above examples)
.
.
u
u
p
p
d
d
a
a
t
t
i
i
n
n
g
g
(
(
)
)
-
-
M
M
o
o
v
v
e
e
V
V
i
i
e
e
w
w
w
w
h
h
i
i
l
l
e
e
d
d
r
r
a
a
g
g
g
g
i
i
n
n
g
g
Method updating()
is called while dragging (to give us information about current position)
accepts @GestureState (as its first parameter)
which can be updated inside trailing Closure (its second Parameter, through Closure's in-out state parameter)
This example shows how to change position of the View as we drag it around which is done by
adding .offset Modifier to the View
which reads @GestureStat dragOffset
which is updated with translation value inside updating() method (every update redraws View)
through in-out state parameter that points to $dragOffset (which must be @GestureState for this to work)
When Drag Gesture stops
View returns to its original position
because location coordinates are reset to startLocation
which resets translation value also to zero (translation = location - startLocation)
which resets dragOffset value also to zero
ContentView.swift
struct ContentView: View {
@GestureState private var dragOffset = CGSize.zero
var body: some View {
Circle()
.fill(Color.green)
.frame(width: 50, height: 50)
.offset(x: dragOffset.width, y: dragOffset.height)
.gesture( DragGesture()
.updating($dragOffset) { (value, state, transaction) in //Trailing Closure for body Parameter
state = value.translation //translation = location - startLocation
}
)
}
}
Initial & final position Position changes as we drag
.
.
o
o
n
n
E
E
n
n
d
d
e
e
d
d
(
(
)
)
-
-
M
M
o
o
v
v
e
e
V
V
i
i
e
e
w
w
w
w
h
h
e
e
n
n
d
d
r
r
a
a
g
g
g
g
i
i
n
n
g
g
e
e
n
n
d
d
s
s
This example shows how to leave View at final position by simply adding finalOffset which is set in .onEnded().
Method onEnded()
is called when we finish dragging
accepts Trailing Closure as its last Parameter which is why Closure can be declared outside Parameters List
DragGesture()
struct ContentView: View {
@State private var finalOffset = CGSize.zero
var body: some View {
Circle()
.fill(Color.green)
.frame(width: 50, height: 50)
.offset(x: finalOffset.width, y: finalOffset.height)
.gesture( DragGesture()
.onEnded { (value) in //Trailing Closure for _ action Parameter
self.finalOffset.height += value.translation.height
self.finalOffset.width += value.translation.width
//self.finalOffset = value.translation //Works only for the first drag
}
)
}
}
Console
Start = (25.0, 13.0)
End = (185.5, -26.5)
C
C
a
a
l
l
c
c
u
u
l
l
a
a
t
t
e
e
d
d
i
i
s
s
t
t
a
a
n
n
c
c
e
e
f
f
r
r
o
o
m
m
t
t
a
a
r
r
g
g
e
e
t
t
In this example we
use DragGesture to drag green circle toward the red circle
calculate distance to see how close we were to the red circle when dragging stops
print different message to the Console depending on the distance
Method onEnded() has trailing Closure which is called when we finish dragging.
Closure is called with Parameter par which holds different information about the dragging.
We are interested in par.location which is CGPoint of cursor position when we stopped dragging.
By calculating distance between par.location CGPoint and destination CGPoint we can see how close we were to red circle
SwiftUI doesn't have Function for calculating distance between two CGPoints so we had to make distanceBetweenPoints()
Drag green circle toward red circle
ContentView.swift
import SwiftUI
//==================================================================================
// VARIABLES
//==================================================================================
var source = CGPoint(x: -100, y:0)
var destination = CGPoint(x: 100, y:0)
var success = "GOOD JOB"
var failure = "NOT CLOSE ENOUGH"
var minimumDistance : CGFloat = 50.0
//==================================================================================
// STRUCT: ContentView
//==================================================================================
struct ContentView : View {
var body : some View {
ZStack {
//SOURCE
Circle()
.fill(Color.green)
.frame(width: 30, height: 30)
.offset(x: source.x, y: source.y)
.gesture( DragGesture()
.onEnded { (value) in //Trailing Closure for body Parameter
let distance = distanceBetweenPoints(destination, value.location)
if (distance < minimumDistance) { print(success) }
else { print(failure) }
})
//DESTINATION
Circle()
.fill(Color.red)
.frame(width: 30, height: 30)
.offset(x: destination.x)
}
}
}
//==================================================================================
// FUNCTION: distanceBetweenPoints()
//==================================================================================
func distanceBetweenPoints (_ point1: CGPoint, _ point2: CGPoint) -> CGFloat {
let deltaX = point1.x - point2.x
let deltaY = point1.y - point2.y
let vector = CGVector(dx: deltaX, dy: deltaY)
let length = hypot(vector.dx, vector.dy); //Vector length is always positive number.
return length
}
Console
GOOD JOB
NOT CLOSE ENOUGH
C
C
o
o
m
m
b
b
i
i
n
n
e
e
d
d
r
r
a
a
g
g
g
g
i
i
n
n
g
g
a
a
n
n
d
d
s
s
t
t
a
a
y
y
i
i
n
n
g
g
This example is combination of previous examples
.updating() - Move View while dragging
.onEnded() - Move View when dragging ends
We need this additional @State variable because @GestureState variable
resets to 0 when Drag finishes (so offset also goes to 0 as shown in previous example)
is not writeable so we can not override it inside .onEnded() (like we can finalOffset)
Initial position Move View while dragging. Final position
ContentView.swift
struct ContentView: View {
@GestureState private var dragOffset = CGSize.zero
@State private var finalOffset = CGSize.zero
var body: some View {
Circle()
.fill(Color.green)
.frame(width: 50, height: 50)
.offset(x: dragOffset.width + finalOffset.width, y: dragOffset.height + finalOffset.height)
.gesture( DragGesture()
.updating($dragOffset) { (value, state, transaction) in //Trailing Closure for body Parameter
state = value.translation //location - startLocation
}
.onEnded { (value) in //Trailing Closure for _ action Parameter
self.finalOffset.height += value.translation.height
self.finalOffset.width += value.translation.width
//self.finalOffset = value.translation //Works only for the first drag
}
)
}
}
C
C
o
o
m
m
b
b
i
i
n
n
e
e
d
d
r
r
a
a
g
g
g
g
i
i
n
n
g
g
,
,
s
s
t
t
a
a
y
y
i
i
n
n
g
g
a
a
n
n
d
d
d
d
i
i
s
s
t
t
a
a
n
n
c
c
e
e
This example is combination of previous examples
.updating() - Move View while dragging
.onEnded() - Move View when dragging ends
Calculate distance from target
Drag green circle toward red circle Calculate distance from red circle
ContentView.swift
import SwiftUI
//==================================================================================
// VARIABLES
//==================================================================================
var source = CGPoint(x: -100, y:0)
var destination = CGPoint(x: 100, y:0)
var success = "GOOD JOB"
var failure = "NOT CLOSE ENOUGH"
var minimumDistance : CGFloat = 50.0
//==================================================================================
// STRUCT: ContentView
//==================================================================================
struct ContentView: View {
@GestureState private var dragOffset = CGSize.zero
@State private var finalOffset = CGSize.zero
var body: some View {
ZStack {
//SOURCE
Circle()
.fill(Color.green)
.frame(width: 50, height: 50)
.zIndex(1)
.offset(x: source.x + finalOffset.width + dragOffset.width, y: source.y + finalOffset.height +
dragOffset.height)
.gesture( DragGesture()
.updating($dragOffset) { (value, state, transaction) in //Trailing Closure for body Parameter
state = value.translation //location - startLocation
}
.onEnded{ (value) in
//STAY AT FINAL POSITION.
self.finalOffset.height += value.translation.height
self.finalOffset.width += value.translation.width
//CALCULATE DISTANCE.
let distance = distanceBetweenPoints(destination, value.location)
if (distance < minimumDistance) { print(success) }
else { print(failure) }
}
)
//DESTINATION
Circle()
.fill(Color.red)
.frame(width: 50, height: 50)
.zIndex(0)
.offset(x: destination.x)
}
}
}
//==================================================================================
// FUNCTION: distanceBetweenPoints()
//==================================================================================
func distanceBetweenPoints (_ point1: CGPoint, _ point2: CGPoint) -> CGFloat {
let deltaX = point1.x - point2.x
let deltaY = point1.y - point2.y
let vector = CGVector(dx: deltaX, dy: deltaY)
let length = hypot(vector.dx, vector.dy); //Vector length is always positive number.
return length
}